/*!
   \file

   \brief

   © 2011 Mark W. Gabby II

   This file is part of the Avion engine.

   The Avion engine is free software: you can redistribute it and/or modify it under the terms of
   the GNU General Public License as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

   The Avion engine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
   the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along with the Avion Engine.
   If not, see <http://www.gnu.org/licenses/>.
*/
#include "Systems/Platform/Platform.hpp"
#include "Systems/Interfaces/PlatformInterface.hpp"

#include "Systems/ErrorManager/ErrorManager.hpp"

extern bool gPlatformInitialized;

namespace pf
{
void MaybeInitialize();

tSendInputMessage gSendInputMessageCallback;
tControllerCaptured gControllerCapturedCallback;
tControllerReleased gControllerReleasedCallback;
tSetModifierState gSetModifierStateCallback;

tAddGraphicsViewport gAddGraphicsViewportCallback;

void SetInputMessageCallback( tSendInputMessage Callback )
{
   gSendInputMessageCallback = Callback;
}

void SetInputControllerCapturedCallback( tControllerCaptured Callback )
{
   gControllerCapturedCallback = Callback;
}

void SetInputControllerReleasedCallback( tControllerReleased Callback )
{
   gControllerReleasedCallback = Callback;
}

void SetInputModifierStateCallback( tSetModifierState Callback )
{
   gSetModifierStateCallback = Callback;
}

void SetAddGraphicsViewportCallback( tAddGraphicsViewport Callback )
{
   gAddGraphicsViewportCallback = Callback;
}
}
namespace iPlatform
{
tPixelFormatIndex SelectPixelFormat( const bool RGBA, const uint8_t BitsPerPixel,
                                     const uint8_t DepthBits, const uint8_t StencilBits,
                                     const uint8_t Multisamples, const uint8_t AccumBits )
{
   pf::MaybeInitialize();

   // Go through all the pixel formats.
   uint32_t formatCount = GetPixelFormatCount();

   uint32_t attrMatches;
   uint32_t attrCloseCnt;

   // Index of best match, matches of best match, close count of best match.
   uint32_t bestMatch[3] = { 0 };

   for ( uint32_t i = 0; i < formatCount; ++i )
   {
      attrMatches = 0;
      attrCloseCnt = 0;

      // Compare each to the desired one.
      PixelFormat temp = GetPixelFormat( i );

      if ( temp.RGBA == RGBA )
      {
         ++attrMatches;
      }

      if ( temp.bitsPerPixel == BitsPerPixel )
      {
         // Attribute match
         ++attrMatches;
      }

      // If we match the depth buffer requested perfectly
      if ( temp.depthBufferBits == DepthBits )
      {
         // Attribute match
         ++attrMatches;
      }
      // If we're requesting a depth buffer AT ALL and it has one
      else if ( DepthBits )
      {
         if ( temp.depthBufferBits )
         {
            ++attrCloseCnt;
         }
      }

      // If we match the multisamples requested perfectly
      if ( temp.multisampleMode == Multisamples )
      {
         // Attribute match
         ++attrMatches;
      }
      // If we're requesting multisamples AT ALL and it has some
      else if ( Multisamples )
      {
         if ( temp.multisampleMode )
         {
            ++attrCloseCnt;
         }
      }

      // Stencil buffer checks.
      if ( temp.stencilBits == StencilBits )
      {
         ++attrMatches;
      }

      else if ( StencilBits )
      {
         if ( temp.stencilBits )
         {
            ++attrCloseCnt;
         }
      }

      if ( temp.accumBufferBits == AccumBits )
      {
         ++attrMatches;
      }
      else if ( AccumBits )
      {
         if ( temp.accumBufferBits )
         {
            ++attrCloseCnt;
         }
      }

      // This matches exactly, return its index.
      if ( attrMatches == 6 )
      {
         return i;
      }
      else
      {
         // Check the current pixel format's match
         // count against the current best one.
         if ( attrMatches > bestMatch[1] )
         {
            // If the current format has a better match
            // count, make it the current best one.
            bestMatch[0] = i;
            bestMatch[1] = attrMatches;
            bestMatch[2] = attrCloseCnt;

         }
         // If they're equal, check the close count.
         else if ( attrMatches == bestMatch[1] )
         {
            // If the close count for the new one is greater
            // than the old one, select the new one.
            if ( attrCloseCnt > bestMatch[2] )
            {
               bestMatch[0] = i;
               bestMatch[1] = attrMatches;
               bestMatch[2] = attrCloseCnt;
            }
         }
      }

   }

   // If there is at least one format
   if ( formatCount )
   {
      // Return the best match so far.
      return bestMatch[0];
   }

   // Otherwise, there aren't any valid formats.
   return kNullPixelFormatIndex;
}

tMonitorIndex GetPrimaryMonitorIndex()
{
   pf::MaybeInitialize();

   const tMonitorIndex MonitorCount = GetMonitorCount();
   for ( tMonitorIndex i = 0; i < MonitorCount; ++i )
   {
      MonitorInfo CurMonitorInfo = GetMonitorInformation( i );
      if ( CurMonitorInfo.primary )
      {
         return i;
      }
   }

   return kNullMonitorIndex;
}

ScreenSettings GetScreenSettings( const bool Fullscreen,
                                  const uint16_t Width, const uint16_t Height,
                                  const int16_t OffsetX, const int16_t OffsetY,
                                  const float AspectRatioOverride, const float FOVDegreesOverride,
                                  const tMonitorIndex MonitorIndex, const tPixelFormatIndex PixelFormatIndex,
                                  const uint8_t ExactRefreshRate, const uint8_t MinRefreshRate )
{
   AssertWarning( !( MinRefreshRate != 0 && ExactRefreshRate != 0 ), "Warning - exact refresh rate set, so minimum refresh rate is being ignored." );

   pf::MaybeInitialize();

   ScreenSettings ReturnValue;

   // In fullscreen, offset ignored. Otherwise, set it here
   ReturnValue.OffsetX = Fullscreen ? 0 : OffsetX;
   ReturnValue.OffsetY = Fullscreen ? 0 : OffsetY;

   if ( PixelFormatIndex == kNullPixelFormatIndex )
   {
      ReturnValue.PixelFormatIndex = SelectPixelFormat( true, 32, 24, 8, 2, 0 );
   }
   else
   {
      ReturnValue.PixelFormatIndex = PixelFormatIndex;
   }

   // Set the desired minimum refresh rate based on the input parameter
   const uint8_t DesiredMinRefreshRate = MinRefreshRate == 0 ? 59 : MinRefreshRate;

   // If the passed-in resolution is invalid:
   ReturnValue.Width = Width;
   ReturnValue.Height = Height;
   if ( MonitorIndex == kNullMonitorIndex )
   {
      ReturnValue.MonitorIndex = GetPrimaryMonitorIndex();
      // No primary monitor, but there are other monitors?
      if ( ReturnValue.MonitorIndex == kNullMonitorIndex && GetMonitorCount() > 0 )
      {
         // Use the first other monitor
         ReturnValue.MonitorIndex = 0;
      }
   }
   else
   {
      ReturnValue.MonitorIndex = MonitorIndex;
   }
   ReturnValue.ModeIndex = kNullScreenModeIndex;

   const uint8_t CurrentRefreshRate = GetMonitorRefreshRate( ReturnValue.MonitorIndex );

   if ( ( Width == 0 || Width == kNull16 ||
          Height == 0 || Height == kNull16 ) &&
         // Don't try to get the default resolution if there's no monitor
         ( ReturnValue.MonitorIndex != kNullMonitorIndex ) )
   {
      const uint32_t FullScreenWidth = GetMonitorWidth( ReturnValue.MonitorIndex );
      const uint32_t FullScreenHeight = GetMonitorHeight( ReturnValue.MonitorIndex );
      // In fullscreen, default to the screen resolution
      if ( Fullscreen )
      {
         ReturnValue.Width = FullScreenWidth;
         ReturnValue.Height = FullScreenHeight;
      }
      // Otherwise, create a window whose area covers most of the screen
      else
      {
         ReturnValue.Width = GetMonitorWorkingWidth( ReturnValue.MonitorIndex );
         ReturnValue.Height = GetMonitorWorkingHeight( ReturnValue.MonitorIndex );
         ReturnValue.Width -= GetBorderWidth() * 2;
         ReturnValue.Height -= GetBorderHeight() * 2 + GetMenuHeight();
      }

      // Set the position of the window correctly
      ReturnValue.OffsetX += GetMonitorLeftCoordinate( ReturnValue.MonitorIndex );
      ReturnValue.OffsetY += GetMonitorTopCoordinate( ReturnValue.MonitorIndex );
   }

   uint32_t LastSelectedModeRefreshRate = 0;
   if ( Fullscreen )
   {
      const tScreenModeIndex ModeCount = GetScreenModesCount( ReturnValue.MonitorIndex );
      for ( tScreenModeIndex ModeIndex = 0; ModeIndex < ModeCount; ++ModeIndex )
      {
         ScreenModeInfo Mode = GetScreenModeInfo( ModeIndex, ReturnValue.MonitorIndex );
         if ( ReturnValue.Width == Mode.dimensions[kWidth] &&
              ReturnValue.Height == Mode.dimensions[kHeight] &&
              32 == Mode.bitsPerPixel &&
              ( DesiredMinRefreshRate <= Mode.refreshRate || Mode.refreshRate == ExactRefreshRate ) )
         {
            // Finally, deal with refresh rate matching:
            // Is this exactly the right refresh rate?
            // Then we're done!
            if ( ExactRefreshRate != 0 && ExactRefreshRate == Mode.refreshRate )
            {
               ReturnValue.ModeIndex = ModeIndex;
               break;
            }
            // Are we not looking for an exact rate?
            else if ( ExactRefreshRate == 0 )
            {
               // By default, keeping choosing the modes with higher refresh rates
               // (min is already accounted for)
               if ( Mode.refreshRate > LastSelectedModeRefreshRate )
               {
                  // Set this as the default to use
                  ReturnValue.ModeIndex = ModeIndex;
                  LastSelectedModeRefreshRate = Mode.refreshRate;
               }

               // However, if we found a mode that matches the current refresh rate, stick with it
               if ( Mode.refreshRate == CurrentRefreshRate )
               {
                  ReturnValue.ModeIndex = ModeIndex;
                  break;
               }
            }
         }
      }
   }

   if ( AspectRatioOverride == 0 )
   {
      ReturnValue.AspectRatio = ( float )( ReturnValue.Width ) / ( float )( ReturnValue.Height );
   }
   else
   {
      ReturnValue.AspectRatio = AspectRatioOverride;
   }
   if ( FOVDegreesOverride == 0 )
   {
      ReturnValue.FOVDegrees = 60.0f;
   }
   else
   {
      ReturnValue.FOVDegrees = FOVDegreesOverride;
   }

   return ReturnValue;
}
}
